#include <dlfcn.h>
#include <malloc.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <alloca.h>

#include "cfd-filter.h"

struct filter * open_filter(const char * name
                            , const char * arg
                            )
{
    if (strchr(name, '/'))
    {
        fprintf(stderr
                , "Bad filter name: '/' found\n"
                );

        return NULL;
    }

    char * sym_name = alloca(strlen(PROVIDE_FUNCTION_MASK)
                             + strlen(name)
                             + 1
                             );

    long filter_lib_size = strlen(name)
                           + strlen(FILTER_PREFIX)
                           + strlen(".so")
                           + 1
                           ;

    char * filter_lib = alloca(filter_lib_size);

    snprintf(filter_lib
             , filter_lib_size
             , FILTER_PREFIX "%s.so"
             , name
             );

    sprintf(sym_name
            , PROVIDE_FUNCTION_MASK
            , name
            );

    void * lib = dlopen(filter_lib, RTLD_LAZY);

    if (NULL == lib)
    {
        fprintf(stderr
                , "Couldn't open filter: %s: %s\n"
                , filter_lib
                , strerror(errno)
                );


        return NULL;
    }

    filter_init constructor = dlsym(lib, sym_name);

    if (NULL == constructor)
    {
        fprintf(stderr
                , "Couldn't find symbol %s in %s\n"
                , sym_name
                , filter_lib
                );

        dlclose(lib);
        return NULL;
    }

    const struct provide * provide = constructor();

    if (NULL == provide)
    {
        fprintf(stderr
                , "Filter couldn't construct filter provide table\n"
                );

        dlclose(lib);
        return NULL;
    }

    struct filter * filter = malloc(sizeof(*filter));

    if (NULL == filter)
    {
        fprintf(stderr
                , "Couldn't allocate memory for filter: %s"
                , strerror(errno)
                );

        dlclose(lib);
        return NULL;
    }


    filter->context = provide->constructor(arg);
    filter->handle  = lib;
    filter->provide = provide;

    return filter;
}


void close_filter(struct filter * filter)
{
    filter->provide->destructor(filter->context);
    dlclose(filter->handle);
    free(filter);
}

int apply_filter(pid_t pid
                  , struct  user_regs_struct * registers
                  , void * text
                  , long * text_size
                  , long * text_addr
                  , void * data
                  , long * data_size
                  , struct filter * filter
                  )
{
    if (filter)
        return
            filter->
            provide->
            filter(pid
                   , registers
                   , text
                   , text_size
                   , text_addr
                   , data
                   , data_size
                   , filter->context
                   );

    /* Default action: ignore filter, if it doesn't present */
    return 0;

}
